/*!	 canvasfilenaming.cpp
**	 CanvasFilenaming Implementation
**
**	......... ... 2016 Ivan Mahonin
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include "canvasfilenaming.h"

#include "filecontainerzip.h"
#include "filesystemnative.h"
#include "filesystemgroup.h"
#include "filesystemtemporary.h"
#include "importer.h"

#endif

using namespace synfig;
using namespace etl;
using namespace std;

const String CanvasFileNaming::container_prefix("#");
const String CanvasFileNaming::container_directory_separator("/");
const String CanvasFileNaming::container_canvas_filename("project.sifz");

String
CanvasFileNaming::filename_base(const String &filename)
{
    String base = filename;
    size_t pos;

    while ((pos = base.find(container_prefix)) != String::npos) {
        base.replace(pos, container_prefix.size(), container_directory_separator);
    }

    return etl::basename(base);
}

String
CanvasFileNaming::filename_extension_lower(const String &filename)
{
    String ext = etl::filename_extension(filename);

    if (!ext.empty()) {
        ext = ext.substr(1);    // skip initial '.'
    }

    std::transform(ext.begin(), ext.end(), ext.begin(), &::tolower);
    return ext;
}

String
CanvasFileNaming::append_directory_separator(const String &path)
{
    return path.empty() ? String() : path + container_directory_separator;
}

String
CanvasFileNaming::content_folder_by_extension(const String &ext)
{
    if (Importer::book().count(ext)) {
        return "images";
    }

    if (ext == "pgo") {
        return "animations";
    }

    return String();
}

bool
CanvasFileNaming::is_container_extension(const String &ext)
{
    return ext == "sfg";
}

String
CanvasFileNaming::make_short_filename(const String &canvas_filename, const String &filename)
{
    if (filename.empty()) {
        return String();
    }

    String clean_filename = etl::cleanup_path(filename);

    if (filename_base(clean_filename).empty()) {
        clean_filename = etl::dirname(clean_filename);
    }

    if (filename_base(clean_filename).empty()) {
        return String();
    }

    // any file inside container accessible without folder:
    //     "#filename.png" equals to "#images/filename.png"
    //     "#filename.pgo" equals to "#animations/filename.pgo"
    if (clean_filename.substr(0, container_prefix.size()) == container_prefix)
        return clean_filename.size() > container_prefix.size()
               ? container_prefix + filename_base(clean_filename.substr(container_prefix.size()))
               : String();

    if (!etl::is_absolute_path(canvas_filename)) {
        return clean_filename;
    }

    String canvas_absolute_filename = etl::absolute_path(canvas_filename);
    String canvas_path = etl::dirname(canvas_absolute_filename);
    String canvas_basename = filename_base(canvas_absolute_filename);
    String absolute_filename = etl::absolute_path(canvas_path, clean_filename);
    String relative_filename = etl::relative_path(canvas_path, absolute_filename);

    // convert "mycanvas.sfg#images/filename.png" to "#filename.png"
    String prefix = canvas_basename + container_prefix;

    if (relative_filename.substr(0, prefix.size()) == prefix)
        return relative_filename.size() > prefix.size()
               ? container_prefix + filename_base(relative_filename.substr(prefix.size()))
               : String();

    if (!is_container_filename(canvas_basename)) {
        // for non-containers convert "mycanvas.images/filename.png" to "#filename.png"
        String content_folder = content_folder_by_filename(clean_filename);
        String prefix = canvas_basename + "." + content_folder + container_directory_separator;

        if (!content_folder.empty()
                && relative_filename.substr(0, prefix.size()) == prefix)
            return relative_filename.size() > prefix.size()
                   ? container_prefix + filename_base(relative_filename.substr(prefix.size()))
                   : String();
    }

    return relative_filename;
}

String
CanvasFileNaming::make_full_filename(const String &canvas_filename, const String &filename)
{
    String short_filename = make_short_filename(canvas_filename, filename);

    if (short_filename.empty()) {
        return String();
    }

    if (filename_base(short_filename).empty()) {
        return String();
    }

    if (short_filename.substr(0, container_prefix.size()) == container_prefix) {
        String content_folder = content_folder_by_filename(filename);
        String content_folder_prefix = !content_folder.empty()
                                       ? content_folder + container_directory_separator
                                       : String();
        return short_filename.size() > container_prefix.size()
               ? container_prefix + content_folder_prefix + filename_base(short_filename.substr(container_prefix.size()))
               : String();
    }

    if (!etl::is_absolute_path(canvas_filename)) {
        return short_filename;
    }

    String canvas_absolute_filename = etl::absolute_path(canvas_filename);
    String canvas_path = etl::dirname(canvas_absolute_filename);
    String absolute_filename = etl::absolute_path(canvas_path, short_filename);

    return absolute_filename;
}

String
CanvasFileNaming::make_canvas_independent_filename(const String &canvas_filename, const String &filename)
{
    String full_filename = make_full_filename(canvas_filename, filename);

    if (full_filename.empty()) {
        return String();
    }

    if (filename_base(full_filename).empty()) {
        return String();
    }

    if (full_filename.substr(0, container_prefix.size()) != container_prefix) {
        return full_filename;
    }

    if (!etl::is_absolute_path(canvas_filename)) {
        return full_filename;
    }

    String canvas_absolute_filename = etl::absolute_path(canvas_filename);

    if (is_container_filename(canvas_absolute_filename)) {
        return canvas_absolute_filename + full_filename;
    }

    String content_folder = content_folder_by_filename(full_filename);

    if (content_folder.empty()) {
        return canvas_absolute_filename + full_filename;
    }

    String canvas_path = etl::dirname(canvas_absolute_filename);
    String canvas_basename = filename_base(canvas_absolute_filename);
    return canvas_path
           + ETL_DIRECTORY_SEPARATOR
           + canvas_basename
           + "."
           + content_folder
           + ETL_DIRECTORY_SEPARATOR
           + filename_base(full_filename.substr(container_prefix.size()));
}

String
CanvasFileNaming::make_local_filename(const String &canvas_filename, const String &filename)
{
    if (filename.empty()) {
        return String();
    }

    String base = filename_base(filename);

    if (base.empty()) {
        base = filename_base(etl::dirname(filename));
    }

    if (base.substr(0, container_prefix.size()) == container_prefix) {
        base = base.substr(container_prefix.size());
    }

    if (!etl::is_absolute_path(canvas_filename)) {
        return base;
    }

    String canvas_absolute_filename = etl::absolute_path(canvas_filename);
    String canvas_path = etl::dirname(canvas_absolute_filename);
    return canvas_path + ETL_DIRECTORY_SEPARATOR + base;
}

FileSystem::Handle
CanvasFileNaming::make_filesystem_container(const String &filename, FileContainerZip::file_size_t truncate_storage_size, bool create_new)
{
    if (is_container_filename(filename)) {
        FileContainerZip::Handle container(new FileContainerZip());

        if (create_new
                ? container->create(filename)
                : container->open_from_history(filename, truncate_storage_size)) {
            return container;
        }
    } else {
        String dir = etl::dirname(filename);
        String base = filename_base(filename);
        String name = etl::filename_sans_extension(base);
        String ext = filename_extension_lower(base);
        String prefix = dir + ETL_DIRECTORY_SEPARATOR + name + ".";

        FileSystemGroup::Handle group(new FileSystemGroup());
        group->register_system("images", FileSystemNative::instance(), prefix + "images");
        group->register_system("animations", FileSystemNative::instance(), prefix + "animations");
        group->register_system(ext == "sif" ? "project.sif" : container_canvas_filename, FileSystemNative::instance(), filename);
        return group;
    }

    return FileSystem::Handle();
}

FileSystem::Handle
CanvasFileNaming::make_filesystem(const FileSystem::Handle &filesystem_container)
{
    if (!filesystem_container) {
        return FileSystem::Handle();
    }

    FileSystemGroup::Handle group(new FileSystemGroup(FileSystemNative::instance()));
    group->register_system(container_prefix, filesystem_container, String(), true);
    return group;
}

FileSystem::Handle
CanvasFileNaming::make_filesystem(const String &filename, FileContainerZip::file_size_t truncate_storage_size, bool create_new)
{
    return make_filesystem(make_filesystem_container(filename, truncate_storage_size, create_new));
}

String
CanvasFileNaming::project_file(const FileSystem::Handle &canvas_filesystem)
{
    if (!canvas_filesystem) {
        return String();
    }

    if (canvas_filesystem->is_file(container_canvas_full_filename())) {
        return container_canvas_full_filename();
    }

    if (canvas_filesystem->is_file(container_prefix + "project.sif")) {
        return container_prefix + "project.sif";
    }

    return String();
}

String
CanvasFileNaming::project_file(const String &filename)
{
    return filename_extension_lower(filename) == "sif"
           ? container_prefix + "project.sif"
           : container_canvas_full_filename();
}

bool
CanvasFileNaming::is_embeded(const String &filename)
{
    String clean_filename = etl::cleanup_path(filename);
    return clean_filename.size() > container_prefix.size()
           && clean_filename.substr(0, container_prefix.size()) == container_prefix;
}

bool
CanvasFileNaming::is_embeded(const String &canvas_filename, const String &filename)
{
    if (canvas_filename.empty()) {
        return is_embeded(filename);
    }

    String short_filename = make_short_filename(canvas_filename, filename);
    return short_filename.size() > container_prefix.size()
           && short_filename.substr(0, container_prefix.size()) == container_prefix;
}

bool
CanvasFileNaming::can_embed(const String &filename)
{
    return !content_folder_by_filename(filename).empty();
}

String
CanvasFileNaming::generate_container_filename(const FileSystem::Handle &canvas_filesystem, const String &filename)
{
    String base = filename_base(filename);

    if (base.empty()) {
        base = filename_base(etl::dirname(filename));
    }

    if (base.substr(0, container_prefix.size()) == container_prefix) {
        base = base.substr(container_prefix.size());
    }

    String ext = filename_extension_lower(base);
    base = etl::filename_sans_extension(base);
    String content_folder = content_folder_by_extension(ext);
    String content_prefix = content_folder.empty() ? String() : content_folder + container_directory_separator;

    for (int i = 1; i < 10000; ++i) {
        String short_filename = i <= 1
                                ? container_prefix + base + "." + ext
                                : etl::strprintf("%s%s_%d.%s", container_prefix.c_str(), base.c_str(), i, ext.c_str());
        String full_filename = i <= 1
                               ? container_prefix + base + "." + ext
                               : etl::strprintf("%s%s%s_%d.%s", container_prefix.c_str(), content_prefix.c_str(), base.c_str(), i, ext.c_str());

        if (!canvas_filesystem->is_exists(full_filename)) {
            return short_filename;
        }
    }

    assert(false);
    return String();
}

String
CanvasFileNaming::container_canvas_full_filename()
{
    return container_prefix + container_canvas_filename;
}